﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

namespace SRTSolutions.Elevate.Guide
{
    using global::System;
    using global::System.Collections.Generic;
    using global::System.Linq;

    using global::NUnit.Framework;

    public class MutableLazyLists
    {
        [Test]
        public void MutableLazyListsGetTheirValuesFromAnIndexerFunction()
        {
            //given a function that generates a value based on an index
            Func<int, string> indexer = index => index.ToString();

            //we can create a mutable lazy list
            IList<string> mutableLazyList = new MutableLazyList<string>(indexer, 100);

            //the values in the list will be populated by applying the indexer function
            Assert.AreEqual("1", mutableLazyList[1]);
            Assert.AreEqual("2", mutableLazyList[2]);
            Assert.AreEqual("65", mutableLazyList[65]);
        }

        [Test]
        public void MutableLazyListsGetTheirCountFromAnInputArgument()
        {
            //we can construct a mutable lazy list as above
            //this time focus on the second arguemnt, the initial of the list
            Func<int, string> indexer = index => index.ToString();
            IList<string> mutableLazyList = new MutableLazyList<string>(indexer, 100);

            Assert.AreEqual(100, mutableLazyList.Count);
            Assert.Throws<ArgumentOutOfRangeException>(() => { var tmp = mutableLazyList[100]; });
        }

        [Test]
        public void MutableLazyListsCallTheIndexerFunctionOnlyOncePerValue()
        {
            //given a mutable lazy list with an indexer function that lets us keep track of how
            //many times it is called
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            //when we index the list
            var result = mutableLazyList[20];

            //the indexer function gets called once per index that we access
            Assert.AreEqual(1, calledCount);
            result = mutableLazyList[10];
            Assert.AreEqual(2, calledCount);
            result = mutableLazyList[16];
            Assert.AreEqual(3, calledCount);

            //but it uses the cached value if we access the same value again
            var result2 = mutableLazyList[16];
            Assert.AreEqual(3, calledCount);
            Assert.AreEqual(result, result2);
        }

        public IList<string> GetMutableLazyList()
        {
            Func<int, string> indexer = index => index.ToString();
            return new MutableLazyList<string>(indexer, 100);
        }

        [Test]
        public void SettingAValueAfterGettingAValue()
        {
            //given a mutable lazy list
            var list = GetMutableLazyList();

            //if we get a value at an index
            var value = list[10];

            //we can set a value at the same index, and it is remembered
            //just like a normal list
            list[10] = "Hello Mutable Lazy List World!";
            Assert.AreEqual("Hello Mutable Lazy List World!", list[10]);
        }

        [Test]
        public void SettingAValueBeforeGettingAValue()
        {
            //given a mutable lazy list with an indexer function that lets us keep track of how
            //many times it is called
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            //if we set a value at an index
            mutableLazyList[14] = "You shall not get called";

            //the indexer function will not get called when we get the value at that index
            var result = mutableLazyList[14];
            Assert.AreEqual(0, calledCount);

            //and the value will be what we set it to
            Assert.AreEqual("You shall not get called", result);
        }

        #region Unit Tests

        [Test]
        public void MutableLazyListsThrowArgumentExceptionForNegativeIndex()
        {
            var list = GetMutableLazyList();
            Assert.Throws<ArgumentOutOfRangeException>(() => { var tmp = list[-1]; });
        }

        [Test]
        public void MutableLazyListConstructorIndexerNullException()
        {
            Assert.Throws<ArgumentNullException>(() => new MutableLazyList<int>(null, 100));
        }

        [Test]
        public void MutableLazyListConstructorCountException()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => new MutableLazyList<int>(x=>x, -1));
        }

        [Test]
        public void MutableLazyListAdd()
        {
            var list = GetMutableLazyList();
            list.Add("alpha");
            Assert.AreEqual("alpha", list[100]);
        }

        [Test]
        public void MutableLazyListIndexOf()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            Assert.AreEqual(30, mutableLazyList.IndexOf("30"));
            Assert.AreEqual(31, calledCount);
        }

        [Test]
        public void MutableLazyListIndexOfFail()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            Assert.AreEqual(-1, mutableLazyList.IndexOf("doesn't exist"));
            Assert.AreEqual(50, calledCount);
        }

        [Test]
        public void MutableLazyListInsert()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            mutableLazyList.Insert(20, "everything after this will need to get evaluated");

            Assert.AreEqual("everything after this will need to get evaluated", mutableLazyList[20]);
            Assert.AreEqual("20", mutableLazyList[21]);
            Assert.AreEqual("21", mutableLazyList[22]);
            Assert.AreEqual("49", mutableLazyList[50]);
            Assert.AreEqual(30, calledCount);
        }

        [Test]
        public void InsertArgumentOutOfRangeNegative()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            Assert.Throws<ArgumentOutOfRangeException>(() => list.Insert(-1, "alpha"));
            Assert.AreEqual(0, calledCount);
        }

        [Test]
        public void InsertMax()
        {
            var list = GetMutableLazyList();

            list.Insert(100, "alpha");
            Assert.AreEqual("alpha", list[100]);
        }

        [Test]
        public void RemoveAt()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            list.RemoveAt(20);
            Assert.AreEqual(49, list.Count);
            Assert.AreEqual("21", list[20]);
            Assert.AreEqual("22", list[21]);
            Assert.AreEqual("23", list[22]);
            Assert.AreEqual("49", list[48]);
            Assert.AreEqual(calledCount, 30);
        }

        [Test]
        public void RemoveAtNegativeOutOfRange()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(-1));
            Assert.AreEqual(0, calledCount);
        }

        [Test]
        public void RemoveAtPositiveOutOfRange()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(50));
            Assert.AreEqual(0, calledCount);
        }

        [Test]
        public void Clear()
        {
            var list = GetMutableLazyList();
            list.Clear();

            Assert.AreEqual(0, list.Count);
            Assert.Throws<ArgumentOutOfRangeException>(() => { var value = list[0]; });
        }

        [Test]
        public void Contains()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            Assert.IsTrue(mutableLazyList.Contains("30"));
            Assert.AreEqual(31, calledCount);
        }

        [Test]
        public void ContainsFail()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var mutableLazyList = new MutableLazyList<string>(indexer, 50);

            Assert.IsFalse(mutableLazyList.Contains("not present"));
            Assert.AreEqual(50, calledCount);
        }

        //Note, not testing all of the edge cases for CopyTo. I'm as lazy as this list.
        [Test]
        public void CopyTo()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            var expected = 50.Times(x => x.ToString());
            var result = new string[70];
            list.CopyTo(result, 20);
            Assert.AreEqual(expected, result.Skip(20));
        }

        [Test]
        public void IsReadOnly()
        {
            Assert.IsFalse(GetMutableLazyList().IsReadOnly);
        }

        [Test]
        public void Remove()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            Assert.IsTrue(list.Remove("20"));
            Assert.AreEqual(49, list.Count);
            Assert.AreEqual(50, calledCount);

            Assert.AreEqual(0.Through(19).Concat(21.Through(49)).Select(x=> x.ToString()), list);
        }

        [Test]
        public void RemoveFail()
        {
            var calledCount = 0;
            Func<int, string> indexer = index => { calledCount++; return index.ToString(); };
            var list = new MutableLazyList<string>(indexer, 50);

            Assert.IsFalse(list.Remove("not present"));
            Assert.AreEqual(50, list.Count);
            Assert.AreEqual(50, calledCount);

            Assert.AreEqual(50.Times(x=>x.ToString()), list);
        }



        #endregion
    }
}
